#pragma once

#include "CAGrids.h"

template <class CellT>
class CAHexagonalGrid : public CAGrid2D<CellT>
{
public:
  CAHexagonalGrid(int width, int height, int bufCount = 1)
    : CAGrid2D<CellT>(width, height, bufCount) 
  {
    assert(height % 2 == 0); // for correct TORUS boundary
  }

  template <class VisitorT>
  void walkAllCells(VisitorT  visitor)
  {
    visitor.walkAll(*this);
  }

  template <class Derived>
  class NeighbourVisitor
  {
  private:
    CAHexagonalGrid * m_grid;

  public:
    typedef const CellT & ConstCellRef;
    typedef const CellT * ConstCellPtr;

    void walkAll(CAHexagonalGrid & grid)
    {
      m_grid = &grid;

      int width = grid.getWidth();
      int height = grid.getHeight();
      
      // walk upper and lower boundaries
      for (int x = 0; x != width; ++x )
      {
        setNeightboursSafe(x, 0);
        static_cast<Derived*>(this)->walkCell();
        setNeightboursSafe(x, height - 1);
        static_cast<Derived*>(this)->walkCell();
      }

      // walk left and right boundaries
      for (int y = 1; y != height - 1; ++y )
      {
        setNeightboursSafe(0, y);
        static_cast<Derived*>(this)->walkCell();
        setNeightboursSafe(width - 1, y);
        static_cast<Derived*>(this)->walkCell();
      }

      // walk all other cells
      for (int y = 1; y != height - 1; ++y)
      {
        setNeightboursSafe(1, y);
        for (int x = 1; x != width - 1; ++x)
        {
          static_cast<Derived*>(this)->walkCell();
          ++m_CR; ++m_UR; ++m_UL; ++m_CL; ++m_DL; ++m_DR; ++m_CC;
          ++m_out; ++m_outId;
        }
      }
    }

  private:
    void setNeightboursSafe(int x, int y)
    {
      int s = y % 2;

      m_CC = &m_grid->getCell(x, y);

      m_CR = &m_grid->getCell(x + 1, y);
      m_CL = &m_grid->getCell(x - 1, y);

      m_UR = &m_grid->getCell(x + s, y + 1);
      m_DR = &m_grid->getCell(x + s, y - 1);
      
      m_UL = &m_grid->getCell(x + s - 1, y + 1);
      m_DL = &m_grid->getCell(x + s - 1, y - 1);

      m_outId = m_grid->pos2CellId(x, y);
      m_out = &m_grid->getMutableCell(m_outId);
    }

    ConstCellPtr m_CR, m_UR, m_UL, m_CL, m_DL, m_DR, m_CC;
    CellT * m_out;
    CellId m_outId;
    
  protected:
    ConstCellRef CR() const { return *m_CR; }
    ConstCellRef UR() const { return *m_UR; }
    ConstCellRef UL() const { return *m_UL; }
    ConstCellRef CL() const { return *m_CL; }
    ConstCellRef DL() const { return *m_DL; }
    ConstCellRef DR() const { return *m_DR; }
    ConstCellRef CC() const { return *m_CC; }
    CellT & out() const { return *m_out; }
    const CellId & outId() const { return m_outId; }
  };
};
